// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "planetaryzone.h"
#include "planetaryscenemanager.h"
#include "planetaryzonerenderable.h"
#include "planetarypage.h"
#include "planetaryoptions.h"
#include "planetaryplanet.h"
#include "planetarylodjoint.h"

namespace Ogre
{
	/** 
	 * Constructor
	 * Sets initial values.
	*/
	PlanetaryZone::PlanetaryZone(PlanetarySceneManager *mgr)
	{
		mSceneMgr = mgr;
		mOptions = mgr->getOptions();
		mParent = NULL;
		mSceneNode = NULL;
		mRenderable = NULL;

		for(int i = 0; i < 4; i++)
			mNeighbours[i] = NULL;

		mZone = 0;
		mIsInit = false;
		mIsLoaded = false;
	}

	/**
	 * Destructor
	 * unloads, uninits and deletes everything
	*/
	PlanetaryZone::~PlanetaryZone()
	{
		if(mIsLoaded)
			unload();
		if(mIsInit)
			uninit();
	}

	/**
	 * Initialises the zone.
	 * Sets up the renderable, and all the pages contained within this zone.
	 * @param zone the index of this zone
	 * @param parent the planet this zone belongs to
	*/
	void PlanetaryZone::init(unsigned int zone,PlanetaryPlanet *parent)
	{
		assert(!mIsInit);
		assert(mPages.empty());

		mParent = parent;

		mZone = zone;

		mPages.reserve(mOptions->ZoneSize);
		mPages.resize(mOptions->ZoneSize);


		// Create Scene Node
		if(!mSceneNode)
		{
			const String name = "Node::" + StringConverter::toString(mZone);
			mSceneNode = mParent->getSceneNode()->createChildSceneNode(name);
		}

		// Create the zone renderable
		mRenderable = new PlanetaryZoneRenderable(mSceneMgr);
		mRenderable->init(mZone,this);
		mRenderable->setVisible(false);

		PlanetaryPage *p;
		for(unsigned int i = 0; i < mOptions->ZoneSize; i++)
		{
			mPages[i].reserve(mOptions->ZoneSize);
			mPages[i].resize(mOptions->ZoneSize,NULL);

			for(unsigned int j = 0; j < mOptions->ZoneSize; j++)
			{
				p = new PlanetaryPage(mSceneMgr);
				p->init(mZone,i,j,this);
				mPages[i][j] = p;
			}
		}

		// Set all neighbours
		for(unsigned int i = 0; i < mOptions->ZoneSize; i++)
		{
			for(unsigned int j = 0; j < mOptions->ZoneSize; j++)
			{
				if(i == 0) // We're at the edge to the west
					mPages[i][j]->setNeighbour(WEST,NULL);
				else
					mPages[i][j]->setNeighbour(WEST,mPages[i-1][j]);

				if(i + 1 >= mOptions->ZoneSize) // We're at the edge to the east
					mPages[i][j]->setNeighbour(EAST,NULL);
				else
					mPages[i][j]->setNeighbour(EAST,mPages[i+1][j]);

				if(j == 0) // We're at the edge to the north
					mPages[i][j]->setNeighbour(NORTH,NULL);
				else
					mPages[i][j]->setNeighbour(NORTH,mPages[i][j-1]);

				if(j + 1 >= mOptions->ZoneSize)
					mPages[i][j]->setNeighbour(SOUTH,NULL);
				else
					mPages[i][j]->setNeighbour(SOUTH,mPages[i][j+1]);
			}
		}

		mIsInit = true;
	}

	/**
	 * Loads the zone.
	 * Loads the renderable connected with this zone, all the pages contained within the zone.
	 * Initialises LOD-joints for use between different LOD-levels.
	*/
	void PlanetaryZone::load()
	{
		assert(mIsInit);
		assert(!mIsLoaded);

		// Init lod-joints
		for(int i = 0; i < 4; i ++)
		{
			mLODJoints[i] = new PlanetaryLODJoint(mSceneMgr);
			mLODJoints[i]->init(mZone,mNeighbours[i]->mZone,(NEIGHBOUR)i,mSceneNode);
		}

		mRenderable->load();

		mRenderable->setVisible(true);
		mSceneNode->attachObject(mRenderable);

		// Load the LOD-joints
		for(int i = 0; i < 4; i ++)
			mLODJoints[i]->load();
	}

	/**
	 * Unloads the zone, and all pages connected with the zone that is currently loaded.
	*/
	void PlanetaryZone::unload()
	{
		mRenderable->unload();

		PlanetaryPage *p;

		for(unsigned int i = 0; i < mOptions->ZoneSize; i++)
		{
			for(unsigned int j = 0; j < mOptions->ZoneSize; j++)
			{
				p = mPages[i][j];
				if(p->isLoaded())
					p->unload();
			}
		}
	}

	/**
	 * Un-initialises the zone, cleans up the renderable and all pages connected with this zone.
	*/
	void PlanetaryZone::uninit()
	{
		assert(!mIsLoaded);
		assert(mIsInit);

		if(mRenderable->isVisible())
			mSceneNode->detachObject(mRenderable);

		mRenderable->uninit();

		delete mRenderable;
		mRenderable = NULL;

		PlanetaryPage *p;

		for(unsigned int i = 0; i < mOptions->ZoneSize; i++)
		{
			for(unsigned int j = 0; j < mOptions->ZoneSize; j++)
			{
				p = mPages[i][j];
				p->uninit();
				delete p;
				mPages[i][j] = NULL;
			}

			mPages[i].clear();
		}

		mPages.clear();

		mSceneNode->removeAndDestroyAllChildren();

		mIsInit = false;
	}

	/**
	 * makes sure that the detail level is set properly for the current camera
	 * @param cam the current camera being used
	*/
	void PlanetaryZone::updatePaging(Camera *cam)
	{
		Sphere camsphere,camloadsphere;

		camsphere.setCenter(cam->getDerivedPosition());
		camsphere.setRadius(mOptions->mZoneToPageLODDistance);

		camloadsphere.setCenter(cam->getDerivedPosition());
		camloadsphere.setRadius(mOptions->mZoneToPageLODDistance * 1.5); // Multiply by 1.5 of the LOD change distance

		if(mRenderable->getBoundingBox().intersects(camloadsphere))
		{
			// Load the pages that's not already loaded
			for(std::vector<std::vector<PlanetaryPage *> >::iterator i = mPages.begin(); i != mPages.end(); i++)
			{
				for(std::vector<PlanetaryPage *>::iterator j = (*i).begin(); j != (*i).end(); j++)
				{
					if(!(*j)->isLoaded() && !(*j)->isLoading())
					{
						mParent->addLoadableToLoadQueue(*j);
					}

					(*j)->touch();
				}
			}

			if(mRenderable->getBoundingBox().intersects(camsphere))
			{
				// Check if we're displaying the zone renderable
				if(mRenderable->isVisible())
				{
					// Make sure all pages are already loaded before we try to view them
					for(std::vector<std::vector<PlanetaryPage *> >::iterator i = mPages.begin(); i != mPages.end(); i++)
					{
						for(std::vector<PlanetaryPage *>::iterator j = (*i).begin(); j != (*i).end(); j++)
						{
							if(!(*j)->isLoaded())
								return;
						}
					}

					// Hide the zone, and notify pages
					mRenderable->setVisible(false);
					mSceneNode->detachObject(mRenderable);

					for(std::vector<std::vector<PlanetaryPage *> >::iterator i = mPages.begin(); i != mPages.end(); i++)
					{
						for(std::vector<PlanetaryPage *>::iterator j = (*i).begin(); j != (*i).end(); j++)
						{
							/*if(!(*j)->isLoaded())
							{
								// This page is about to get rendered, make sure it's loaded!
								(*j)->load();
							}*/

							(*j)->show(true);
							(*j)->updatePaging(cam);
						}
					}

					// Notify self first
					_notifySelf();

					for(int i = 0; i < 4; i++)
						mNeighbours[i]->_notifyNeighbour(translateNeighbour(mZone,(NEIGHBOUR)i),this);
				}
				else
				{
					for(std::vector<std::vector<PlanetaryPage *> >::iterator i = mPages.begin(); i != mPages.end(); i++)
					{
						for(std::vector<PlanetaryPage *>::iterator j = (*i).begin(); j != (*i).end(); j++)
						{
							(*j)->updatePaging(cam);
						}
					}
				}
			}
			else
			{
				// Check if we're displaying the pages
				if(!mRenderable->isVisible())
				{
					mRenderable->setVisible(true);
					mSceneNode->attachObject(mRenderable);

					for(std::vector<std::vector<PlanetaryPage *> >::iterator i = mPages.begin(); i != mPages.end(); i++)
					{
						for(std::vector<PlanetaryPage *>::iterator j = (*i).begin(); j != (*i).end(); j++)
						{
							(*j)->show(false);
						}
					}

					// Notify self
					_notifySelf();

					for(int i = 0; i < 4; i++)
						mNeighbours[i]->_notifyNeighbour(translateNeighbour(mZone,(NEIGHBOUR)i),this);
				}
			}
		}
		else
		{
			// Unload any pages that's not needed anymore
			for(std::vector<std::vector<PlanetaryPage *> >::iterator i = mPages.begin(); i != mPages.end(); i++)
			{
				for(std::vector<PlanetaryPage *>::iterator j = (*i).begin(); j != (*i).end(); j++)
				{
					if(((*j)->isLoaded() || (*j)->isLoading()) && (*j)->unloadUntouched() && !(*j)->isUnloading())
					{
						mParent->addLoadableToUnloadQueue(*j);
					}
				}
			}
		}
	}

	/**
	 * Sets the material of this zone's renderable, the all the sub-pages contained within the zone
	 * @param mat the material to set
	*/
	void PlanetaryZone::setMaterial(MaterialPtr mat)
	{
		mRenderable->setMaterial(mat);

		if(!mRenderable->isVisible())
		{
			for(unsigned int i = 0; i < mPages.size(); i++)
				for(unsigned int j = 0; j < mPages[i].size(); j++)
					mPages[i][j]->setMaterial(mat);
		}
	}

	/**
	 * Sets which zones are neighbours of this zone, uses cardinal directions (north, south, west, east)
	 * @param n the direction to the neighbour
	 * @param p pointer to the zone in that direction
	*/
	void PlanetaryZone::setNeighbour(NEIGHBOUR n, PlanetaryZone *p)
	{
		mNeighbours[n] = p;
	}

	/**
	 * Function called when one of the zones neighbours changes the LOD-level, and forces this zone to
	 * evaluate if it needs to display LOD-joints or not
	 * @param n the direction of the calling neighbour
	 * @param callingNeighbour the zone creating the notification
	*/
	void PlanetaryZone::_notifyNeighbour(NEIGHBOUR n,PlanetaryZone *callingNeighour)
	{
		// Check if there neighbours have the same LOD level visible
		if(callingNeighour->mRenderable->isVisible() == mRenderable->isVisible())
		{
			// Hide joints
			mLODJoints[n]->show(false);
		}
		else
		{
			// Check if we're in the position to display lod-joints
			if(!mRenderable->isVisible())
			{
				mLODJoints[n]->show(false);
			}
			else
			{
				mLODJoints[n]->show(true);
			}
		}
	}

	/**
	 * notifies all neighbours about a LOD-change, and shows LOD-joints if it is necessary.
	*/
	void PlanetaryZone::_notifySelf()
	{
		for(unsigned int i = 0; i < 4; i ++)
		{
			if(!mNeighbours[i]->mRenderable->isVisible() && mRenderable->isVisible())
			{
				if(!mLODJoints[i]->isVisible())
					mLODJoints[i]->show(true);
			}
			else if(!mNeighbours[i]->mRenderable->isVisible() && !mRenderable->isVisible())
			{
				if(mLODJoints[i]->isVisible())
					mLODJoints[i]->show(false);
			}
		}
	}
}
